package terrain;

import main.*;

import java.awt.Button;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.Line2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;

public class LowWallRect extends Terrain{
	
	public static final int FULLY_BLOCKED = 2;
	public static final int PARTIAL_BLOCKED = 1;
	public static final int NOT_BLOCKED = 0;
	
	private double minDim;
	private Line2D reflectLine;
	
	//
	private Area lastSight;
	private Area lastRotate;
	private Area lastUnitCheck;
	//
	
	public LowWallRect(int x1, int y1, double width, double height, double rotationCW) {
		Shape s = new Rectangle2D.Double(x1, y1, width, height);
		obstacle = new Area(s);
		AffineTransform rotate = AffineTransform.getRotateInstance(rotationCW, x1, y1);
		obstacle.transform(rotate);
		
		this.minDim = Math.min(width, height);
		if (width < height) {
			double[] srcPts = {x1+width/2, y1, x1+width/2, y1+height};
			double[] dstPts = {0,0,0,0};
			rotate.transform(srcPts, 0, dstPts, 0, 2);
			reflectLine = new Line2D.Double(dstPts[0], dstPts[1], dstPts[2], dstPts[3]);
		}
		else {
			double[] srcPts = {x1, y1+height/2, x1+width, y1+height/2};
			double[] dstPts = {0,0,0,0};
			rotate.transform(srcPts, 0, dstPts, 0, 2);
			reflectLine = new Line2D.Double(dstPts[0], dstPts[1], dstPts[2], dstPts[3]);
		}
		//System.out.println(reflectLine.getX1() + ", " + reflectLine.getY1() + "     " + reflectLine.getX2() + ", " + reflectLine.getY2());
	}

	public Area getObstacle() {
		// TODO Auto-generated method stub
		return this.obstacle;
	}
	
	/*
	 * Return values:
	 * 		0 / NOT_BLOCKED - sight of target not blocked by obstacle
	 * 		1 / PARTIAL_BLOCKED - sight of target partially blocked by obstacle
	 * 		2 / FULLY_BLOCKED - sight fully blocked
	 */
	public int blocksLOS(Unit looker, Unit target){
		double theta = Math.atan2(target.y-looker.y, target.x-looker.x);
		double dist = Math.sqrt(Math.pow(target.x-looker.x, 2) + Math.pow(target.y-looker.y, 2));

		// Make an Area representing sight line, clone it for two tests
		Polygon sightTriangle = new Polygon();
		sightTriangle.addPoint(looker.x, looker.y);
		sightTriangle.addPoint((int) (looker.x+dist), looker.y-target.pixelRadius);
		sightTriangle.addPoint((int) (looker.x+dist), looker.y+target.pixelRadius);
		Area sightArea = new Area(sightTriangle);
		
		lastSight = (Area)sightArea.clone();
		AffineTransform rotate = AffineTransform.getRotateInstance(theta, looker.x, looker.y);
		sightArea.transform(rotate);
		lastRotate = (Area)sightArea.clone();
		
		// If intersecting is non-empty, then partially blocked
		sightArea.intersect(obstacle);
		if(!sightArea.isEmpty()) {
			return PARTIAL_BLOCKED;
		}
		
		// For units: If partially blocked but in contact, can nullify that penalty 
		// If fully blocked, still a problem.
		return NOT_BLOCKED;
	}
	
	
	public Point2D contactPoint(int rootX, int rootY, int destX, int destY, int pixelRadius) {
		//System.out.println("Checking contact with line ("+rootX+", "+rootY+") and ("+destX+", " + destY+ ")");
		double slope = 1000000000;
		if(destX != rootX)
			slope = (destY-rootY)/(destX-rootX);
		double intercept = rootY - slope * rootX;
		
		PathIterator pi  = obstacle.getPathIterator(null);
		double[] coords = {0,0,0,0,0,0};
		
		int moveType = pi.currentSegment(coords);
		double prevX = coords[0];
		double prevY = coords[1];
		double currX = 0;
		double currY = 0;
		double candidateX = 0;
		double candidateY = 0;
		Point2D rootPoint = new Point2D.Double(rootX, rootY);
		Point2D closePoint = new Point2D.Double(-1, -1);
		pi.next();
		
		while (moveType != PathIterator.SEG_CLOSE){
			moveType = pi.currentSegment(coords);
			currX = coords[0];
			currY = coords[1];
			if (currY == prevY && currX == prevX){
				pi.next();
				continue;
			}
			
			// If we intersect, or any of the endpoints are within a base radius, we're going to contact this obstacle
			if (Utils.segmentIntersect(rootX, rootY, destX, destY, prevX, prevY, currX, currY) ||
					Utils.distanceFromLine(destX, destY, prevX, prevY, currX, currY, false) <= pixelRadius-1 ||
					Utils.distanceFromLine(prevX, prevY, rootX, rootY, destX, destY, false) <= pixelRadius-1 ||
					Utils.distanceFromLine(currX, currY, rootX, rootY, destX, destY, false) <= pixelRadius-1) {
				//System.out.println("It's close enough!L");
				Line2D wallSeg = new Line2D.Double(prevX, prevY, currX, currY);
				// Calculate the point we hit at with a simple line intersection with a parallel line
				// to the obstacle one
				if (currX == prevX) {   // Target line is vertical
					if (rootX < currX)
						candidateX = currX-pixelRadius;   // Approaching from left
					else
						candidateX = currX+pixelRadius;   // Approaching from right
					if (closePoint.getX() == -1 && 
							wallSeg.ptSegDist(candidateX, slope*candidateX + intercept) <= pixelRadius+2){
						closePoint = new Point2D.Double(candidateX, slope*candidateX + intercept);
					}
					else {
						candidateY = slope*candidateX + intercept;
						if (rootPoint.distance(candidateX, candidateY) < rootPoint.distance(closePoint) && 
								wallSeg.ptSegDist(candidateX, slope*candidateX + intercept) <= pixelRadius+2) {
							closePoint = new Point2D.Double(candidateX, candidateY);
						}
					}
				}
				else if (currY == prevY) {   // Target line is horizontal
					if (rootY < currY)
						candidateY = currY-pixelRadius;   // Approaching from below
					else
						candidateY = currY+pixelRadius;   // Approaching from right
					if (closePoint.getX() == -1 && 
							wallSeg.ptSegDist((candidateY-intercept)/slope, candidateY) <= pixelRadius+2){
						if (slope == 0)
							slope += 0.000001;
						closePoint = new Point2D.Double((candidateY-intercept)/slope, candidateY);
					}
					else {
						if (slope == 0)
							slope += 0.000001;
						candidateX = (candidateY-intercept)/slope;
						if (rootPoint.distance(candidateX, candidateY) < rootPoint.distance(closePoint) && 
								wallSeg.ptSegDist((candidateY-intercept)/slope, candidateY) <= pixelRadius+2) {
							closePoint = new Point2D.Double(candidateX, candidateY);
						}
					}
				}
				else {      // Normal wall line
					double wallSlope = (currY-prevY)/(currX-prevX);
					double wallInt = currY - wallSlope*currX;
					//System.out.println("Base wall slope and intersect:" + wallSlope + " " + wallInt);
					double yChange = Math.abs(pixelRadius/Math.cos(Math.atan2(currY-prevY, currX-prevX)));
					//System.out.println("atan2: " + Math.atan2(currY-prevY, currX-prevX));
					//System.out.println("ychange: " + yChange);
					double newInt;
					if (rootY <= wallSlope*rootX + wallInt) { // Below the line
						newInt = wallInt - yChange;
					}
					else{
						newInt = wallInt + yChange;
					}
					//System.out.println("Extended y-intersect: " + newInt);
					Point2D intersect = Utils.findLineIntersect(rootX, rootY, destX, destY, currX, currX*wallSlope+newInt, prevX, prevX*wallSlope+newInt);
					//System.out.println("Intersect point: (" + intersect.getX() + ", " + intersect.getY()+")");
					if(wallSeg.ptSegDist(intersect) > pixelRadius+2) {
						prevX = currX;
						prevY = currY;
						pi.next();
						continue; // There will be a better one.
					}
					
					if (closePoint.getX() == -1) {
						//System.out.println("Also, it's closer!");
						closePoint = (Point2D)intersect.clone();
					}
					else if (rootPoint.distance(intersect) < rootPoint.distance(closePoint)) {
						//System.out.println("Also, it's closer!");
						closePoint = (Point2D)intersect.clone();
					}
				}
			}					
			prevX = currX;
			prevY = currY;
			pi.next();
		}
		
		//System.out.println("Closest contact point: " + closePoint.getX() + " " + closePoint.getY());
		if (closePoint.getX() == -1)
			return null;
		return closePoint;
	}
	
	public void contactedByUnit(Unit u) {
		// If we're narrow enough to enable fighting across the barrier
		if (this.minDim <= Utils.pixelsPerInch){
			// Check to see if there is an opponent across the barrier
			ArrayList<Unit> closeEnemies = new ArrayList<Unit>();
			
			for (Unit opp : this.contactUnits) {
				if (opp.armyIndex != u.armyIndex && Utils.pixelDist(u, opp) <= Math.sqrt(Math.pow(u.pixelRadius+opp.pixelRadius,2) + 
																						Math.pow(u.pixelRadius+opp.pixelRadius + minDim, 2))) {
					Line2D line = new Line2D.Double(u.x,u.y, opp.x, opp.y);

					if(line.intersectsLine(reflectLine)){
						closeEnemies.add(opp);
					}
				}
			}
			// If there's only one enemy blocking us, either start a fight with him or join his existing fight
			if (closeEnemies.size() == 1) {
				//System.out.println("Enemy unit behind the barrier!");
				Unit opp = closeEnemies.get(0);
				if(opp.battle == null) {
					// Create a new Barrier battle
					u.moveLeft = 0;
					opp.moveLeft = 0;
					BarrierBattle b = new BarrierBattle(u.game, this);
					b.addUnit(u);
					b.addUnit(opp);
					b.setDefender(opp);
					u.game.battles.add(b);
					u.battle = b;
					opp.battle = b;
				}
				else {
					if (opp.battle instanceof BarrierBattle){
						opp.battle.addUnit(u);
						u.battle = opp.battle;
						u.moveLeft = 0;
					}
					else {
						opp.battle.addUnit(u);
						u.battle = opp.battle;
						u.moveLeft = 0;
						/*
						BarrierBattle b = new BarrierBattle(u.game, this);
						b.importBattle(opp.battle);
						u.game.battles.remove(opp.battle);
						b.setDefender(opp);
						b.addUnit(u);
						u.game.battles.add(b);
						u.battle = b;
						*/
					}
				}
			}
			// Otherwise, if any of the close enemies are free, start a new fight with them
			else if (closeEnemies.size() > 1) {
				ArrayList<Unit> freeOpps = new ArrayList<Unit>();
				for (Unit opp : closeEnemies) {
					if (opp.battle == null)
						freeOpps.add(opp);
				}
				if (freeOpps.size() > 0) {
					u.moveLeft = 0;
					BarrierBattle b = new BarrierBattle(u.game, this);
					b.addUnit(u);
					for (Unit opp : freeOpps) {
						opp.moveLeft = 0;
						b.addUnit(opp);
						b.setDefender(opp);
						opp.battle = b;
					}
					u.game.battles.add(b);
					u.battle = b;
				}
				// Otherwise, find the closest opponent, whose battle we're going to rearrange
				else {
					Unit closestOpp = closeEnemies.get(0);
					for (Unit opp : closeEnemies) {
						if (Utils.inchDist(u, opp) < Utils.inchDist(u, closestOpp))
							closestOpp = opp;
					}
					// If the closest opponent is fighting solo, add ourselves to the fight against him.
					if (closestOpp.battle.getUnitsByIndex(closestOpp.armyIndex).size() == 1) {
						if (closestOpp.battle instanceof BarrierBattle){
							closestOpp.battle.addUnit(u);
							u.battle = closestOpp.battle;
							u.moveLeft = 0;
						}
						else {
							BarrierBattle b = new BarrierBattle(u.game, this);
							b.importBattle(closestOpp.battle);
							u.game.battles.remove(closestOpp.battle);
							b.setDefender(closestOpp);
							b.addUnit(u);
							u.game.battles.add(b);
							u.battle = b;
						}
					}
					// Otherwise, split him out of his current fight to fight us
					else {
						BarrierBattle b = new BarrierBattle(u.game, this);
						b.addUnit(u);
						u.moveLeft = 0;
						closestOpp.battle.removeUnit(closestOpp);
						b.addUnit(closestOpp);
						closestOpp.battle = b;
						b.setDefender(closestOpp);
						u.game.battles.add(b);
						u.battle = b;
					}
				}
			}
		}
	
		// Update data structures 
		this.contactUnits.add(u);
	}
	
	
	public boolean enemyAcrossBarrier(Unit u, int x, int y) {
		if (this.minDim <= Utils.pixelsPerInch){
			// Check to see if there is an opponent across the barrier
			for (Unit opp : this.contactUnits) {
				if (opp.armyIndex != u.armyIndex && Utils.pixelDist(x,y,opp.x,opp.y) <= Math.sqrt(Math.pow(u.pixelRadius+opp.pixelRadius,2) + 
																								Math.pow(u.pixelRadius+opp.pixelRadius + minDim, 2))) {
					Line2D line = new Line2D.Double(x,y, opp.x, opp.y);
	
					if(line.intersectsLine(reflectLine)){
						return true;
					}
				}
			}
		}
		return false;
	}
	
	
	public Button getInteraction(Unit unit) {
		// TODO Issue: Button will still be there after moving away from the barrier but not selecting new unit. FIX
		
		// Need to first get reflection point and verify there aren't any other units we'd jump onto.
		double slope;
		if (reflectLine.getX1() == reflectLine.getX2())
			slope = 100000000;
		else
			slope = (reflectLine.getY1() - reflectLine.getY2()) / (reflectLine.getX1() - reflectLine.getX2());
		double intercept = reflectLine.getY1() - slope*reflectLine.getX1();
		
		double x = unit.x;
		double y = unit.y;
		
		double d = (x + (y-intercept)*slope) / (1+slope*slope);
		int newX = (int) (2*d-x);
		int newY = (int) (2*d*slope - y + 2*intercept);
		
		// Check for space
		if (!unit.game.canPlace(unit, newX, newY)) {
			System.out.println("Can't place on other side");
			return null;
		}
		
		// Check that we have enough move for the jump
		if (unit.moveLeft < Utils.inchDist(unit.x,unit.y, newX, newY))
			return null;
		
		Button jumpButton = new Button("Jump Barrier");
		jumpButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				board.game.myUI.storeVisibleComponents();
				board.game.myUI.disableComponents();
				
				DieRoll jumpRoll = unit.jumpRoll();
				
				// If we can use might, make it an option
				if (jumpRoll.isModifiable()) {
					ActionListener mightDone = new ActionListener() {
						public void actionPerformed(ActionEvent e) {
							int jumpNum = jumpRoll.getRoll();
							UI.messageLog.add(unit.toChoiceString() + " attempting to jump barrier with roll of " + jumpNum);
							if (jumpNum < 6) {
								unit.moveLeft = 0;
							}
							if (jumpNum > 1) {
								unit.uncontactUnits();
								unit.moveLeft = Math.max(0, unit.moveLeft - Utils.inchDist(unit.x,unit.y, newX, newY));
								unit.x = newX;
								unit.y = newY;
							}
							unit.game.myUI.selectUnit(unit);
							jumpRoll.cleanup();
							unit.game.myUI.disableMight();
							board.game.myUI.reenableComponents();
							unit.game.myUI.repaint();
						}
					};
					unit.game.myUI.enableMight(mightDone);
				}
				else {
					int jumpNum = jumpRoll.getRoll();
					UI.messageLog.add(unit.toChoiceString() + " attempting to jump barrier with roll of " + jumpNum);
					if (jumpNum < 6) {
						unit.moveLeft = 0;
					}
					if (jumpNum > 1) {
						unit.uncontactUnits();
						unit.moveLeft = Math.max(0, unit.moveLeft - Utils.inchDist(unit.x,unit.y, newX, newY));
						unit.x = newX;
						unit.y = newY;
					}
					unit.game.myUI.selectUnit(unit);
					jumpRoll.cleanup();
					board.game.myUI.reenableComponents();
					unit.game.myUI.repaint();
				}
				unit.game.myUI.repaint();
			}
		});
		return jumpButton;
	}
	
	public void takeBarrier(Unit unit) {
		if (!this.contactUnits.contains(unit)) {
			return;
		}
		// Need to first get reflection point and verify there aren't any other units we'd jump onto.
		double slope;
		if (reflectLine.getX1() == reflectLine.getX2())
			slope = 100000000;
		else
			slope = (reflectLine.getY1() - reflectLine.getY2()) / (reflectLine.getX1() - reflectLine.getX2());
		double intercept = reflectLine.getY1() - slope*reflectLine.getX1();
		
		double x = unit.x;
		double y = unit.y;
		
		double d = (x + (y-intercept)*slope) / (1+slope*slope);
		int newX = (int) (2*d-x);
		int newY = (int) (2*d*slope - y + 2*intercept);
		
		if (!unit.game.canPlace(unit, newX, newY))
			return;
		
		unit.uncontactUnits();
		unit.x = newX;
		unit.y = newY;
	}
	
	public void drawTo(Graphics g){
		AffineTransform offsetShift = AffineTransform.getTranslateInstance(-1*board.game.myUI.boardOffsetX, -1*board.game.myUI.boardOffsetY);
		Area drawClone = (Area) obstacle.clone();
		drawClone.transform(offsetShift);
		((Graphics2D)g).setColor(Color.LIGHT_GRAY);
		((Graphics2D)g).fill(drawClone);
		((Graphics2D)g).setColor(Color.BLACK);
		((Graphics2D)g).drawLine((int)reflectLine.getP1().getX()-board.game.myUI.boardOffsetX, (int)reflectLine.getP1().getY()-board.game.myUI.boardOffsetY,
									(int)reflectLine.getP2().getX()-board.game.myUI.boardOffsetX, (int)reflectLine.getP2().getY()-board.game.myUI.boardOffsetY);
		/*if (UI.DEBUG)
			drawLastCheck(g);*/
		
	}
	
	// Debug methods
	public void drawLastCheck(Graphics g){
		if(lastSight != null && lastRotate != null){
			((Graphics2D)g).fill(lastSight);
			((Graphics2D)g).fill(lastRotate);
		}
		if (lastUnitCheck != null)
			((Graphics2D)g).fill(lastUnitCheck);
		
	}
	
	public String toMessageString() {
		return "LowWall";
	}
	
}